
import * as React from "react";

const { useState, useEffect, useRef } = React;

// 键盘事件 keyCode 别名
const aliasKeyCodeMap = {
  '0': 48,
  '1': 49,
  '2': 50,
  '3': 51,
  '4': 52,
  '5': 53,
  '6': 54,
  '7': 55,
  '8': 56,
  '9': 57,
  backspace: 8,
  tab: 9,
  enter: 13,
  shift: 16,
  ctrl: 17,
  alt: 18,
  pausebreak: 19,
  capslock: 20,
  esc: 27,
  space: 32,
  pageup: 33,
  pagedown: 34,
  end: 35,
  home: 36,
  leftarrow: 37,
  uparrow: 38,
  rightarrow: 39,
  downarrow: 40,
  insert: 45,
  delete: 46,
  a: 65,
  b: 66,
  c: 67,
  d: 68,
  e: 69,
  f: 70,
  g: 71,
  h: 72,
  i: 73,
  j: 74,
  k: 75,
  l: 76,
  m: 77,
  n: 78,
  o: 79,
  p: 80,
  q: 81,
  r: 82,
  s: 83,
  t: 84,
  u: 85,
  v: 86,
  w: 87,
  x: 88,
  y: 89,
  z: 90,
  leftwindowkey: 91,
  rightwindowkey: 92,
  selectkey: 93,
  numpad0: 96,
  numpad1: 97,
  numpad2: 98,
  numpad3: 99,
  numpad4: 100,
  numpad5: 101,
  numpad6: 102,
  numpad7: 103,
  numpad8: 104,
  numpad9: 105,
  multiply: 106,
  add: 107,
  subtract: 109,
  decimalpoint: 110,
  divide: 111,
  f1: 112,
  f2: 113,
  f3: 114,
  f4: 115,
  f5: 116,
  f6: 117,
  f7: 118,
  f8: 119,
  f9: 120,
  f10: 121,
  f11: 122,
  f12: 123,
  numlock: 144,
  scrolllock: 145,
  semicolon: 186,
  equalsign: 187,
  comma: 188,
  dash: 189,
  period: 190,
  forwardslash: 191,
  graveaccent: 192,
  openbracket: 219,
  backslash: 220,
  closebracket: 221,
  singlequote: 222,
};

// 修饰键
const modifierKey = {
  ctrl: (event) => event.ctrlKey,
  shift: (event) => event.shiftKey,
  alt: (event) => event.altKey,
  meta: (event) => event.metaKey, // mac的command键
};

// 根据 event 计算激活键数量
function countKeyByEvent(event) {
  const countOfModifier = Object.keys(modifierKey).reduce((total, key) => {
    if (modifierKey[key](event)) { // 有ctrl, meta, alt, shift修饰键
      return total + 1;
    }

    return total;
  }, 0);

  // 比如ctrl.a, 此时countOfModifier = 1, [16, 17, 18, 91, 92].includes(event.keyCode) 返回false
  // 所以最终返回countOfModifier + 1，即返回2
  // 16 17 18 91 92 是修饰键的 keyCode，如果 keyCode 是修饰键，那么激活数量就是修饰键的数量，如果不是，那么就需要 +1
  return [16, 17, 18, 91, 92].includes(event.keyCode) ? countOfModifier : countOfModifier + 1;
}

/**
 * 判断按键是否激活
 * @param [event]键盘事件
 * @param [keyFilter: any] 当前键
 * @returns Boolean
 */
function genFilterKey(event, keyFilter, exactMatch) {
  // 浏览器自动补全 input 的时候，会触发 keyDown、keyUp 事件，但此时 event.key 等为空
  if (!event.key) { // key为空
    return false;
  }

  // 数字类型直接匹配事件的 keyCode
  if (typeof keyFilter === 'number') { // 数字类型
    return event.keyCode === keyFilter;
  }

  // 字符串依次判断是否有组合键
  // ctrl.a
  const genArr = keyFilter.split('.');
  let genLen = 0;

  for (const key of genArr) {
    // 组合键
    // ctrl, shift, alt, meta
    const genModifier = modifierKey[key];

    // keyCode 别名
    const aliasKeyCode = aliasKeyCodeMap[key.toLowerCase()];

    // 是修饰键 或者 code相同
    if ((genModifier && genModifier(event)) || (aliasKeyCode && aliasKeyCode === event.keyCode)) {
      genLen++;
    }
  }

  /**
   * 需要判断触发的键位和监听的键位完全一致，判断方法就是触发的键位里有且等于监听的键位
   * genLen === genArr.length 能判断出来触发的键位里有监听的键位
   * countKeyByEvent(event) === genArr.length 判断出来触发的键位数量里有且等于监听的键位数量
   * 主要用来防止按组合键其子集也会触发的情况，例如监听 ctrl+a 会触发监听 ctrl 和 a 两个键的事件。
   * 比如：注册了 ctrl.a  按下了 ctrl + shift + a
   */
//   debugger
  if (exactMatch) { // 严格相等
    return genLen === genArr.length && countKeyByEvent(event) === genArr.length;
  }

  return genLen === genArr.length;
}

/**
 * 键盘输入预处理方法
 * @param [keyFilter: any] 当前键
 * @returns () => Boolean
 */
function genKeyFormater(keyFilter, exactMatch) {
  if (typeof keyFilter === 'function') { // 函数
    return keyFilter;
  }

  if (typeof keyFilter === 'string' || typeof keyFilter === 'number') { // 字符串 或者 数字
   
    return (event) => genFilterKey(event, keyFilter, exactMatch);
  }
  
  if (Array.isArray(keyFilter)) {// 数组
    return (event) =>
      keyFilter.some((item) => genFilterKey(event, item, exactMatch));
  }

  // 其他类型，存在keyFilter的返回true
  return keyFilter ? () => true : () => false;
}

const defaultEvents = ['keydown'];

const getTargetElement = (ref) => typeof ref === 'function' ? ref() : ref?.current

/**
 * 监听键盘按键，支持组合按键
 * 原理通过keycode判断
 */
function useKeyPress(keyFilter, eventHandler, option) {
  const { events = defaultEvents, target, exactMatch = false } = option || {};

  // 保存最新的事件处理函数
  const eventHandlerRef = useRef(eventHandler);

  // 保存最新的按键过滤
  // keyFilter: string | number 或者 (string | number) [] 或者 function
  const keyFilterRef = useRef(keyFilter);

  useEffect(
    () => {
      // 获取真实的节点，默认是window
      const el = window
      if (!el) {
        return;
      }

      const callbackHandler = (event) => {
        const genGuard = genKeyFormater(keyFilterRef.current, exactMatch);
        if (genGuard(event)) {
          // 执行真正的处理函数
          return eventHandlerRef.current?.(event);
        }
      };

      for (const eventName of events) {
        // 使用el监听所有传入的事件类型
        el?.addEventListener?.(eventName, callbackHandler);
      }
      return () => {
        // 函数销毁的时候，全部移除监听
        for (const eventName of events) {
          el?.removeEventListener?.(eventName, callbackHandler);
        }
      };
    },
    [events],
  );
}


export default () => {
    const [counter, setCounter] = useState(0);
  
    useKeyPress('shift.a', () => {
      setCounter((s) => s + 1);
    });
  
    // keyCode value for ArrowDown
    useKeyPress(40, () => {
      setCounter((s) => s - 1);
    });
  
    return (
      <div>
        <p>Try pressing the following: </p>
        <div>1. Press ArrowUp by key to increase</div>
        <div>2. Press ArrowDown by keyCode to decrease</div>
        <div>
          counter: <span style={{ color: '#f00' }}>{counter}</span>
        </div>
      </div>
    );
  };